
/*-------------------------------------------------------*/
/* High Level Sound Routines.                            */
/* [c]copyright 1995 by AlphaHelix                       */
/*                                                       */
/*                                                       */
/*                                                       */
/*-------------------------------------------------------*/

// Master SOUND ENABLE SWITCH (debugging reasons)
//#define NO_SOUND

#include <stdlib.h>

#include "mikmod\mikmod.h"

#include "error.hpp"
#include "memory.hpp"
#include "diskio.hpp"
#include "sound.hpp"

// Mod player variables.
static UNIMOD  *mf = 0;          // Module file.
static int     mod_voices;       // Number of allocated MOD voices.
static int     mod_play;         // MOD is playing(1) or stopped(0).
static int     mod_vol;          // Volume of MOD voices.

// Wave variables.
static int     wav_voices;       // Number of active WAVE voices.
static int     wav_base;         // First available WAVE voice.
static int     wav_vol;          // WAVE volume.

// Used to store the priority level of every SFX voice.
static int     wav_priority[MAXVOICES];

// All other variables.
static int     halt = 1;         // Some kind of semaphore variable ?
static int     initialized = 0;

// Interrupt Handler.
void tickhandler(void)
{
   if (!halt && mod_play && mod_vol) {
      MP_HandleTick();     // Play 1 tick of the module.
      MD_SetBPM(mp_bpm);   // Tell sound system at which BPM mod wants to run.
   }
}

// Function for internal use.
static void _startsystem(void)
{
// Initialize MOD file if one has been loaded.
   if (mf) MP_Init(mf);
// Start to play.
   MD_PlayStart();
   halt = 0;
}

static void _stopsystem(void)
{
   halt = 1;
// Stop Sound.
   MD_PlayStop();
}

static void _setvoices(int voices)
{
   _stopsystem();
   if (voices == 0) voices = 1;
   md_numchn = voices;
   _startsystem();
}

static void activevoices(void)
{
   int   voices = 0;

   if (mod_vol) voices += mod_voices;
   wav_base = voices;
   if (wav_vol) voices += wav_voices;

   _setvoices(voices);
}

//-------------------------------------------------------------------
// WavePool members.
//-------------------------------------------------------------------
void WavePool::load(char *f)
{
   myFile   myfile;
   int      i;

// read in header
   if (!myfile.open(f)) error("WavPool not found: ", f);
   myfile.read(&nsamples, sizeof(long));
// Just quit if empty library.
   if (nsamples == 0) {
      myfile.close();
      return;
   }
   sample = new Sample[nsamples];
   myfile.read(sample, nsamples * sizeof(Sample));

// Load WAVES.
   for (i = 0; i < nsamples; i++) {
#ifndef NO_SOUND
      _mm_setiobase(myfile.getbase() + (long)sample[i].sample);
      if (!(sample[i].sample=MW_LoadWavFP(myfile.getFILE()))) error("Error loading sample.");
#endif
   }
   myfile.close();

}

void WavePool::unload(void)
{
   int   i;

   if (nsamples == 0) return;

   for (i = 0; i < nsamples; i++) {
#ifndef NO_SOUND
      MW_FreeWav(sample[i].sample);
#endif
   }
   delete []sample;
}

void WavePool::play(int n)
{
   int      i;
   int      busy, pri;
   SAMPLE   *s = sample[n].sample;

   if (!wav_vol) return;
#ifndef NO_SOUND
// First scan if there is a FREE voice.
   for (i = 0; i < wav_voices; i++) {
      if (!(busy = MD_VoiceBusy(wav_base+i))) break;
   }
   if (busy) {
// No free voice found. So pick one we want to drop.
      pri = MINPRIORITY;
      do {
         for (i = 0; i < wav_voices; i++) {
            if (wav_priority[i] == pri) { busy = 0; break; }
         }
         --pri;
      } while (busy && (pri>=sample[n].priority));
   }
   if (!busy) {
      wav_priority[i] = sample[n].priority;
      i += wav_base;
      MD_VoiceSetFrequency(i, s->c2spd);
      MD_VoiceSetVolume(i, (sample[n].volume*wav_vol) >> 6);
      MD_VoiceSetPanning(i, 128);
      MD_VoicePlay(i, s->handle, 0, s->length, 0, 0, s->flags);
   }
#endif
}



//-------------------------------------------------------------------
// MOD functions.
//-------------------------------------------------------------------


/*---------------------------------------------------------
 Function:

 Description:
 Load a MOD or UNIMOD file into memory and prepare it to
 be played.
---------------------------------------------------------*/
void s_loadmod(char *f)
{
#ifndef NO_SOUND
   myFile   myfile;

   if (mf) s_unloadmod();
   if (!myfile.open(f)) error("(s_loadmod): not found: ", f);
   _mm_setiobase(myfile.getbase());
   mf = ML_LoadFP(myfile.getFILE());
   myfile.close();
   if (!mf) error("(s_loadmod): ", myerr);
   if (mod_voices != mf->numchn) {
      mod_voices = mf->numchn;
      activevoices();
   }
   MP_Init(mf);
#endif
}

/*---------------------------------------------------------
 Function:

 Description:
 Unload a previously loaded MOD file from memory.
---------------------------------------------------------*/
void s_unloadmod(void)
{
#ifndef NO_SOUND
   s_stopmod();
   if (mf) ML_Free(mf);
   mf = 0;
#endif
}

/*---------------------------------------------------------
 Function: startmod & stopmod

 Description:
 Start and stop playing a previous loaded MUSIC file
 respectivly.
---------------------------------------------------------*/
void s_startmod(void)
{
   if (mf) mod_play = 1;
}

void s_stopmod(void)
{
#ifndef NO_SOUND
   int   i;

// Mute MOD voices.
   mod_play = 0;
   for (i = 0; i < mod_voices; i++) {
      MD_VoiceSetVolume(i, 0);
   }
#endif
}

char *s_modname(void)
{
#ifdef NO_SOUND
   return "debug mode";
#else
   return mf ? mf->songname : "no song loaded";
#endif
}

/*---------------------------------------------------------
 Function:

 Description:
 Setup system to mix the given number of voices.
---------------------------------------------------------*/
void s_setvoices(int voices)
{
   if (voices > MAXVOICES) voices = MAXVOICES;
#ifndef NO_SOUND
   if (voices != wav_voices) {
      wav_voices = voices;
      activevoices();
   }
#endif
}

/*---------------------------------------------------------
 Function:

 Description:
 Get current mixing frequency in Hz.
---------------------------------------------------------*/
int s_getmixfreq(void)
{
   return md_mixfreq;
}

/*---------------------------------------------------------
 Function:

 Description:
 Set MUSIC volume. Must be in the range (0 to 64).
 If 0 is given, all the voices used by the MOD won't be
 mixed anymore. So this saves a lot of power on a SoundBlaster.
---------------------------------------------------------*/
void s_setmodvol(int volume)
{
#ifndef NO_SOUND
   if ((mod_vol==0 || volume==0) && mod_vol!=volume) {
      mod_vol = volume;
      activevoices();
   }
   mod_vol = volume;
   mp_volume = mod_vol;
#endif
}

int s_getmodvol(void)
{
   return mod_vol;
}

/*---------------------------------------------------------
 Function:

 Description:
 Set the volume to play any WAV files.
 Must be in the range 0 to 64.
 If 0 is given, all the WAV voices are releases saving
 a lot of cpu power with a non hardware-mixing SoundCard.
---------------------------------------------------------*/
void s_setwavvol(int volume)
{
#ifndef NO_SOUND
   if ((wav_vol==0 || volume==0) && wav_vol!=volume) {
      wav_vol = volume;
      activevoices();
   }
   wav_vol = volume;
#endif
}


/*---------------------------------------------------------
 Function:

 Description:
 Returns Sound Card Driver Name.
---------------------------------------------------------*/
char *s_drivername(void)
{
#ifdef NO_SOUND
   return "Debug Mode";
#else
   return md_driver->Name;
#endif
}

/*---------------------------------------------------------
 Function:

 Description:
 Call this function once in a while to update the
 player.
---------------------------------------------------------*/
void s_update(void)
{
#ifndef NO_SOUND
   if (!halt) MD_Update();
#endif
}

/*---------------------------------------------------------
 Function:

 Description:
 Initialize sound system.
 hw       : Which Hardware to initialize. 0 = autodetect.
 mix_freq : Mixing frequency to use.
---------------------------------------------------------*/
void initsound(int hw, int mix_freq)
{
#ifndef NO_SOUND
   if (initialized) return;
// Register the loaders we want to use.
   ML_RegisterLoader(&load_uni);

// Register the drivers we want to use:
   MD_RegisterDriver(&drv_gus);     // Ultrasound
   MD_RegisterDriver(&drv_sb);      // Soundblaster
   MD_RegisterDriver(&drv_ss);      // Soundscape
   MD_RegisterDriver(&drv_nos);     // Nosound.

// Register ModulePlayer we want to use.
   MD_RegisterPlayer(tickhandler);

// Preset sound card paramteres.
   md_mixfreq      = mix_freq;
   md_dmasamples   = md_mixfreq / 10;
   md_mode         = DMODE_16BITS;  // Use 16 bit mono mixing.
   md_device       = hw;

// Preset module player parameters.
   mp_extspd = 1;             // Enable extended SPEED control.
   mp_panning = 0;            // Disable panning.
   mp_loop = 1;               // Loop module by default.

// Initialize several more or less cool variables.
   mf = 0;                 // No MOD loaded.
   mod_play = 0;           // No MOD has been loaded so don't play it.
   wav_voices = 1;         // Start with 1 Wave voice...
   mod_voices = 4;         // ...and 4 MOD voices.
   md_numchn = 1;
   mp_volume = mod_vol = wav_vol = 0;

// initialize soundcard
   if (!MD_Init()) {
      error("Sound Driver Error: ", myerr);
   } else {
      initialized = 1;
      _startsystem();
   }
#endif
}

void shutsound(void)
{
   if (!initialized) return;
#ifndef NO_SOUND
   s_unloadmod();
   _stopsystem();
   MD_Exit();
#endif
   initialized = 0;
}

